//
//  ProjectsPipeline.swift
//  GerritHermes
//
//  Created by J.Zhou on 2020/6/22.
//  Copyright © 2020 周剑. All rights reserved.
//

import Foundation
import Combine

final class ProjectsPipeline {
    private struct ProjectInterval {
        let project: String
        private var interval: TimeInterval = MinTimeInterval
        var timestamp: TimeInterval = 0

        init(project: String) {
            self.project = project
        }

        func shouldHandle() -> Bool {
            let timeInterval = Date().timeIntervalSince1970 - timestamp
            return timeInterval >= interval
        }

        mutating func update(hitCount: Int) {
            // 预期每分钟能处理一个
            let targetCount = Int(interval / 60)
            let hitRate = Float(hitCount) / Float(targetCount)
            if hitRate <= 0.3 {
                interval = min(interval * 2, MaxTimeInterval)
            } else if hitRate <= 0.6 {
                interval = min(interval * 1.5, MaxTimeInterval)
            } else if hitRate >= 1 {
                interval = max(interval / 2, MinTimeInterval)
            }
            Log.debug("Current project \(project) interval: \(interval)")
        }
    }

    private let gerritApi: GerritApi
    private let bag = CancellableBag()

    private var isBusy = false
    private var projects = [String]()
    private var onlineProjects = [String]()
    private var intervalMap = [String: ProjectInterval]()
    private var pipelineMap = [String: ChangesPipeline]()

    init(gerritApi: GerritApi) {
        self.gerritApi = gerritApi

        // TODO: process in global queue
        Timer.publish(every: 1, on: .main, in: .common)
            .autoconnect()
            .beginWith(value: Date())
            .sink { [weak self] (_) in
                self?.handleNext()
            }.cancel(by: bag)
    }

    func update(projects: [String]) {
        self.projects = projects
    }

    private func handleNext() {
        if onlineProjects.isEmpty {
            onlineProjects = projects
        }

        guard !isBusy else { return }
        let _onlineProjects = onlineProjects
        for (index, project) in _onlineProjects.enumerated().reversed() {
            let projectInterval = intervalMap[project] ?? ProjectInterval(project: project)
            onlineProjects.remove(at: index)
            if projectInterval.shouldHandle() {
                isBusy = true
                Log.debug("Project \(projectInterval.project) is busy now")
                process(interval: projectInterval)
                break
            }
        }
    }

    private func process(interval: ProjectInterval) {
        var interval = interval
        interval.timestamp = Date().timeIntervalSince1970

        let pipeline = pipelineMap[interval.project] ?? ChangesPipeline(project: interval.project, gerritApi: gerritApi)
        pipelineMap[interval.project] = pipeline
        pipeline.process { [weak self] hitCount in
            interval.update(hitCount: hitCount)
            self?.intervalMap[interval.project] = interval
            self?.isBusy = false
            Log.debug("Project \(interval.project) is free now")
        }
    }
}
